﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Xml;
using Qurihara;

namespace LogAnalyzeBase
{
    public class LogAnalyzeBase
    {
        #region ExportViewSyncLog
        public static void ExportViewSyncLog(Stream rawLog, Stream outLog,TimeSpan logInterval, bool cutBySetView)
        {
            string str = "";
            using (StreamReader sr = new StreamReader(rawLog))
            {
                str = sr.ReadToEnd();
                sr.Close();
            }
            if (str == "") return;
            DateTime startTime = DateTime.Now;//仮割り当て
            bool first = true;
            bool setViewAppeared = false;
            DateTime lastSetView = DateTime.Now;//仮割り当て
            List<List<Rectangle>> list = new List<List<Rectangle>>();
            List<string> nameList = new List<string>();
            List<DateTime> timeList = new List<DateTime>();

            string[] lines = str.Split('\n');
            foreach (string line in lines)
            {
                string[] ele = line.Split(',');
                if (ele.Length != 7) continue;
                DateTime timeStamp = DateTime.Parse(ele[0]);
                string name = ele[1];
                string msg = ele[2];
                Rectangle rd = new Rectangle(
                    int.Parse(ele[3]),
                    int.Parse(ele[4]),
                    int.Parse(ele[5]),
                    int.Parse(ele[6]));

                if (msg.Equals("set_view"))
                {
                    setViewAppeared = true;
                    name = "presenter";
                    msg = "view";
                    lastSetView = timeStamp;
                }

                if (!setViewAppeared && cutBySetView) continue;

                if (first)
                {
                    startTime = timeStamp;
                    timeList.Add(startTime);
                    first = false;
                }
                else
                {
                    while (timeStamp.Subtract(startTime) >= logInterval)
                    {
                        DateTime dt = startTime.Add(logInterval);
                        startTime = dt;
                        timeList.Add(startTime);
                        // ここでかくnameのデータをアップデート
                        foreach (List<Rectangle> li in list)
                        {
                            Rectangle r = new Rectangle(0, 0, 0, 0);//デフォルトではzeroデータ
                            if (li.Count > 0) r = li[li.Count - 1];
                            li.Add(r);
                        }

                    }
                }


                if (!msg.Equals("view")) continue;
                // set_viewの最新値を蓄えていれば直接KLが計算可能.特殊な名前の人として実装すると楽そう。

                if (!nameList.Contains(name))
                {
                    nameList.Add(name);
                    List<Rectangle> li2 = new List<Rectangle>();
                    list.Add(li2);
                    // これまでの時刻のカラデータを付与する
                    for (int i = 0; i < timeList.Count; i++)
                    {
                        li2.Add(new Rectangle(0, 0, 0, 0));
                    }
                }

                // nameの最新値を更新する。
                int index = nameList.IndexOf(name);
                List<Rectangle> tgt = list[index];
                tgt.RemoveAt(tgt.Count - 1);
                tgt.Add(rd);
            }

            //ファイルをエクスポート
            using (StreamWriter sw = new StreamWriter(outLog))
            {
                //凡例
                sw.Write("timestamp,");
                foreach (string name in nameList)
                {
                    //sw.Write(name + "_X");
                    //sw.Write(",");
                    //sw.Write(name + "_Y");
                    //sw.Write(",");
                    //sw.Write(name + "_W");
                    //sw.Write(",");
                    //sw.Write(name + "_H");
                    //sw.Write(",");
                    if (cutBySetView)
                    {
                        //sw.Write(name + "_KL");
                        //sw.Write(",");
                        //sw.Write(name + "_MUS1");
                        //sw.Write(",");
                        //sw.Write(name + "_MUS2");
                        //sw.Write(",");
                        sw.Write(name + "_SMUS2");
                        sw.Write(",");
                        sw.Write(name + "_SMUS2x2");
                        sw.Write(",");
                    }
                }
                sw.Write(sw.NewLine);

                //データ本体
                for (int i = 0; i < timeList.Count; i++)
                {
                    DateTime ct = timeList[i];
                    sw.Write(ct.ToString() + ",");
                    Rectangle presenR = new Rectangle(0, 0, 0, 0);
                    if (cutBySetView == true)
                    {
                        int index = nameList.IndexOf("presenter");
                        presenR = list[index][i];
                    }
                    foreach (List<Rectangle> li3 in list)
                    {
                        Rectangle cr = li3[i];
                        //sw.Write(cr.X.ToString() + ",");
                        //sw.Write(cr.Y.ToString() + ",");
                        //sw.Write(cr.Width.ToString() + ",");
                        //sw.Write(cr.Height.ToString() + ",");
                        if (cutBySetView)
                        {
                            //double kl = GetKLFromRects(presenR, cr);
                            //sw.Write(kl.ToString() + ",");
                            //double mu1 = Get1NormDistanceFromRects(presenR, cr,true);
                            //sw.Write(mu1.ToString() + ",");
                            //double mu2 = Get2NormDistancePow2FromRects(presenR, cr,true);
                            //sw.Write(mu2.ToString() + ",");
                            double smu2 = Get2NormDistancePow2FromRects(presenR, cr, true) - 1d / 6d;
                            if (smu2 < 0d) smu2 = 0d;
                            smu2 = Math.Sqrt(smu2);
                            sw.Write(smu2.ToString() + ",");

                            double smu22 = Get2D2NormDistancePow2FromRects(presenR, cr, true) - 1d / 3d;
                            if (smu22 < 0d) smu22 = 0d;
                            smu22 = Math.Sqrt(smu22);
                            sw.Write(smu22.ToString() + ",");

                            //if (ct.Equals(DateTime.Parse("2008/12/3  14:48:25")))// && smu2 == double.NaN)
                            //{
                            //    System.Diagnostics.Debug.Fail("why?");
                            //}
                        }
                    }
                    sw.Write(sw.NewLine);
                    if (cutBySetView && ct >= lastSetView) break;
                }
                sw.Close();
            }
        }
        public static double GetKLFromRects(Rectangle orig, Rectangle dest)
        {
            // 標準化する。
            double la, lb, mu;
            GetParams(orig, dest,false, out la, out lb, out mu);
            // 距離を出す。
            double kl = 1d/2d * ( 2d *  Math.Log(lb/la) + ( 1d + (4d*mu/la)*(4d*mu/la))/ ((lb/la)*(lb/la)) -1d); 
            return kl;
        }
        private static double Distance(Point a, Point b)
        {
            double x = (double)a.X - (double)b.X;
            double y = (double)a.Y - (double)b.Y;
            return Math.Sqrt(x*x + y*y);
            // intだと47104*47104でマイナスになってしまう。
            //return Math.Sqrt((a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y));
        }
        private static void GetParams(Rectangle orig, Rectangle dest,bool standardize, out double la, out double lb, out double mu)
        {
            la = Math.Sqrt(orig.Width * orig.Width + orig.Height * orig.Height);
            lb = Math.Sqrt(dest.Width * dest.Width + dest.Height * dest.Height);
            mu = Distance(new Point(orig.X + orig.Width / 2, orig.Y + orig.Height / 2),
                new Point(dest.X + dest.Width / 2, dest.Y + dest.Height / 2));
            if (standardize)
            {
                lb = lb / la;
                mu = mu / la;
                la = 1d;
            }
        }
        private static void GetParams2(Rectangle orig, Rectangle dest, bool standardize, out double la, out double lb, out double mu)
        {
            // origの縦横比を1:1になるように変換し、その１辺を代表長さとする。

            if (!standardize) System.Diagnostics.Debug.Fail("only support standardized output.");
            if (orig.Height == 0)
            {
                la = lb = mu = 0;
                return;
            }

            double aspect = (double)orig.Width / (double)orig.Height;

            orig = new Rectangle((int)(orig.X / aspect), orig.Y, (int)(orig.Width / aspect), orig.Height);
            dest = new Rectangle((int)(dest.X / aspect), dest.Y, (int)(dest.Width / aspect), dest.Height);

            la = orig.Height;
            lb = dest.Height;
            mu = Distance(new Point(orig.X + orig.Width / 2, orig.Y + orig.Height / 2),
                new Point(dest.X + dest.Width / 2, dest.Y + dest.Height / 2));

            lb = lb / la;
            mu = mu / la;
            la = 1d;

        }
        public static double Get1NormDistanceFromRects(Rectangle orig, Rectangle dest, bool standadize)
        {
            double la, lb, mu;
            GetParams2(orig, dest,standadize, out la, out lb, out mu);
            if (la > lb)
            {
                GetParams(dest,orig,standadize, out la, out lb, out mu);
            }

            if (la == 0d || lb == 0d) return double.NaN;

            double det1 = mu - lb / 2d - la / 2d;
            double det2 = mu - lb / 2d + la / 2d;
            double dd = 0;
            if (det1 >= 0)
            {
                if (det2 >= 0)
                {
                    //1
                    dd = mu;
                }
                else
                {
                    System.Diagnostics.Debug.Fail("unable to come here");
                }
            }
            else
            {
                if (det2 >= 0)
                {
                    //2
                    dd = 1d / (24d * la * lb) * (la * la * la 
                        - 6d * la * la * (mu - lb / 2d)
                        + 12d * la * (mu + lb / 2d) * (mu + lb / 2d)
                        - 8d * (mu - lb / 2d) * (mu - lb / 2d) * (mu - lb / 2d));
                }
                else
                {
                    //3
                    dd = 1d / (12d * lb) * (12d * mu * mu + 3d * lb * lb + la * la);
                }
            }

            return dd;
        }
        public static double Get2NormDistancePow2FromRects(Rectangle orig, Rectangle dest,bool standardize)
        {
            double la, lb, mu;
            GetParams2(orig, dest,standardize, out la, out lb, out mu);

            if (la == 0d || lb == 0d)
            {
                return double.NaN;
            }
            double dd = 1d / 12d * (la * la + 12d * mu * mu + lb * lb);
            return dd;
        }

        public static double Get2D2NormDistancePow2FromRects(Rectangle orig, Rectangle dest, bool standardize)
        {
            double w,h, lx,ly;

            if (orig.Width == 0 || orig.Height == 0 || dest.Width == 0 || dest.Height == 0) return double.NaN;
            w = (double)dest.Width / (double)orig.Width;
            //w = w * w;
            h = (double)dest.Height / (double)orig.Height;
            //h = h * h;

            lx = (double)dest.X + (double)dest.Width / 2d - (double)orig.X - (double)orig.Width / 2d;
            lx = lx / (double)orig.Width;
            //lx = lx * lx;

            ly = (double)dest.Y + (double)dest.Height / 2d - (double)orig.Y - (double)orig.Height / 2d;
            ly = ly / (double)orig.Height;
            //ly = ly * ly;
            
            double dd = 1d / 12d * (2 + 12d * (lx * lx + ly * ly) + w*w + h*h);
            return dd;
        }

#endregion

        #region ExportAcrossViewStrokesLog
        public static void ExportAcrossViewStrokesLog(Stream viewLog, Stream rawLog, Stream outLog, TimeSpan logInterval, bool cutBySetView)
        {
            ViewSnapList vsl = LoadViewSnap(viewLog);

            string str = "";
            using (StreamReader sr = new StreamReader(rawLog))
            {
                str = sr.ReadToEnd();
                sr.Close();
            }
            if (str == "") return;

            List<string> nameList = new List<string>();
            List<Rectangle> sendViewRectList = new List<Rectangle>();
            List<Rectangle> strokeRectList = new List<Rectangle>();

            string[] lines = str.Split('\n');
            foreach (string line in lines)
            {
                string[] ele = line.Split(',');
                if (ele.Length != 7) continue;
                DateTime timeStamp = DateTime.Parse(ele[0]);
                string name = ele[1];
                string msg = ele[2];
                Rectangle rd = new Rectangle(
                    int.Parse(ele[3]),
                    int.Parse(ele[4]),
                    int.Parse(ele[5]),
                    int.Parse(ele[6]));

                if (msg.Equals("set_view"))
                {
                    sendViewRectList.Add(rd);
                }
                if (msg.Equals("stroke"))
                {
                    strokeRectList.Add(rd);
                }

                if (!nameList.Contains(name))
                {
                    nameList.Add(name);
                }

            }

            // 指標計算
            int nViews = vsl.Count;// sendViewRectList.Count; // 全view数(本当はviewsnaplistでやるべき）
            int nStrokes = strokeRectList.Count; // 全ストローク数
            int nUser = nameList.Count; // 全ユーザ数
            int nOutOfViewStrokes = 0; //発表者のビューから外れているストローク数
            int nViewIntersectStrokes = 0;//発表者のビューを一部含むが全部は含まれないストローク数
            int nAcrossViewStrokes = 0; //発表者のビューをまたいでいるストローク数

            // for nOutOfViewStrokes
            foreach (Rectangle rs in strokeRectList)
            {
                bool contained = false;
                //foreach (Rectangle rv in sendViewRectList)
                foreach (ViewSnap vs in vsl)
                {
                    Rectangle rv = vs.ViewRectangle;
                    if (rv.Contains(rs))
                    {
                        contained = true;
                        break;
                    }
                }
                if (!contained) nOutOfViewStrokes++;
            }

            // for nAcrossViewStrokes
            foreach (Rectangle rs in strokeRectList)
            {
                bool across = false;
                //foreach (Rectangle rv in sendViewRectList)
                foreach (ViewSnap vs in vsl)
                {
                    Rectangle rv = vs.ViewRectangle;
                    if (rv.IntersectsWith(rs) && !rv.Contains(rs))
                    {
                        //foreach(Rectangle rv2 in sendViewRectList){
                        foreach(ViewSnap vs2 in vsl){
                            Rectangle rv2 = vs2.ViewRectangle;
                            if (rv2.Equals(rv)) continue;
                            if(rv2.IntersectsWith(rs) && !rv2.Contains(rs)){
                                across = true;
                                break;
                            }
                        }

                        break;
                    }
                }
                if (across) nAcrossViewStrokes++;
            }

            foreach (Rectangle rs in strokeRectList)
            {
                bool intersected = false;
                //foreach (Rectangle rv in sendViewRectList)
                foreach (ViewSnap vs in vsl)
                {
                    Rectangle rv = vs.ViewRectangle;
                    if (rv.IntersectsWith(rs) && !rv.Contains(rs))
                    {
                        intersected = true;
                        break;
                    }
                }
                if (intersected) nViewIntersectStrokes++;
            }


            //ファイルをエクスポート
            using (StreamWriter sw = new StreamWriter(outLog))
            {
                sw.Write("nViews, " + nViews.ToString() + "," + sw.NewLine);
                sw.Write("nUser, " + nUser.ToString() + "," + sw.NewLine);
                sw.Write("nStrokes, " + nStrokes.ToString() + "," + sw.NewLine);
                sw.Write("nOutOfViewStrokes, " + nOutOfViewStrokes.ToString() + "," + sw.NewLine);
                sw.Write("nViewIntersectStrokes, " + nViewIntersectStrokes.ToString() + "," + sw.NewLine);
                sw.Write("nAcrossViewStrokes, " + nAcrossViewStrokes.ToString() + "," + sw.NewLine);

                sw.Write("nStrokes/nUser, " + ((double)((double)nStrokes/(double)nUser)).ToString() + "," + sw.NewLine);
                sw.Write("nOutOfViewStrokes/nUser, " + ((double)((double)nOutOfViewStrokes/(double)nUser)).ToString() + "," + sw.NewLine);
                sw.Write("nViewIntersectStrokes/nUser, " + ((double)((double)nViewIntersectStrokes/(double)nUser)).ToString() + "," + sw.NewLine);
                sw.Write("nAcrossViewStrokes/nUser, " + ((double)((double)nAcrossViewStrokes/(double)nUser)).ToString() + "," + sw.NewLine);

                sw.Write("nStrokes/nUsers/nViews, " + ((double)((double)nStrokes / (double)nUser/(double)nViews)).ToString() + "," + sw.NewLine);
                sw.Write("nOutOfViewStrokes/nUsers/nViews, " + ((double)((double)nOutOfViewStrokes / (double)nUser / (double)nViews)).ToString() + "," + sw.NewLine);
                sw.Write("nViewIntersectStrokes/nUsers/nViews, " + ((double)((double)nViewIntersectStrokes / (double)nUser / (double)nViews)).ToString() + "," + sw.NewLine);
                sw.Write("nAcrossViewStrokes/nUsers/nViews, " + ((double)((double)nAcrossViewStrokes / (double)nUser / (double)nViews)).ToString() + "," + sw.NewLine);

                sw.Close();
            }
        }

        private static ViewSnapList LoadViewSnap(Stream inst)
        {
            ViewSnapList vsl = new ViewSnapList();
            using (XmlTextReader xtr = new XmlTextReader(inst))
            {
                vsl.Load(xtr, null);
            }

            return vsl;
        }
#endregion

        #region ExportLinearityLog
        public static void ExportLinearityLog(Stream rawLog, Stream outLog, TimeSpan logInterval)
        {
            string str = "";
            using (StreamReader sr = new StreamReader(rawLog))
            {
                str = sr.ReadToEnd();
                sr.Close();
            }
            if (str == "") return;
            DateTime startTime = DateTime.Now;//仮割り当て
            bool first = true;
            bool setViewAppeared = false;
            DateTime lastSetView = DateTime.Now;//仮割り当て
            List<List<Rectangle>> list = new List<List<Rectangle>>();
            List<string> nameList = new List<string>();
            List<DateTime> timeList = new List<DateTime>();

            string[] lines = str.Split('\n');
            foreach (string line in lines)
            {
                string[] ele = line.Split(',');
                if (ele.Length != 7) continue;
                DateTime timeStamp = DateTime.Parse(ele[0]);
                string name = ele[1];
                string msg = ele[2];
                Rectangle rd = new Rectangle(
                    int.Parse(ele[3]),
                    int.Parse(ele[4]),
                    int.Parse(ele[5]),
                    int.Parse(ele[6]));

                if (msg.Equals("set_view"))
                {
                    setViewAppeared = true;
                    name = "presenter";
                    //msg = "view";
                    lastSetView = timeStamp;
                }

                if (!setViewAppeared) continue;

                if (first)
                {
                    startTime = timeStamp;
                    timeList.Add(startTime);
                    first = false;
                }
                else
                {
                    while (timeStamp.Subtract(startTime) >= logInterval)
                    {
                        DateTime dt = startTime.Add(logInterval);
                        startTime = dt;
                        timeList.Add(startTime);
                        // ここでかくnameのデータをアップデート
                        foreach (List<Rectangle> li in list)
                        {
                            Rectangle r = new Rectangle(0, 0, 0, 0);//デフォルトではzeroデータ
                            if (li.Count > 0) r = li[li.Count - 1];
                            li.Add(r);
                        }

                    }
                }


                if (!msg.Equals("set_view")) continue;

                if (!nameList.Contains(name))
                {
                    nameList.Add(name);
                    List<Rectangle> li2 = new List<Rectangle>();
                    list.Add(li2);
                    // これまでの時刻のカラデータを付与する
                    for (int i = 0; i < timeList.Count; i++)
                    {
                        li2.Add(new Rectangle(0, 0, 0, 0));
                    }
                }

                // nameの最新値を更新する。
                int index = nameList.IndexOf(name);
                List<Rectangle> tgt = list[index];
                tgt.RemoveAt(tgt.Count - 1);
                tgt.Add(rd);
            }

            //ファイルをエクスポート
            using (StreamWriter sw = new StreamWriter(outLog))
            {
                //凡例
                sw.Write("timestamp,");
                sw.Write("presenter_cos_H");
                sw.Write(",");
                sw.Write("presenter_cos_V");
                sw.Write(",");
                sw.Write(sw.NewLine);


                //データ本体
                bool first2 = true;
                Rectangle prevR = new Rectangle();
                for (int i = 0; i < timeList.Count; i++)
                {
                    Rectangle presenR = new Rectangle(0, 0, 0, 0);
                    int index = nameList.IndexOf("presenter");
                    presenR = list[index][i];
                    if (first2)
                    {

                        first2 = false;
                        //DateTime ct = timeList[i];
                        //sw.Write(ct.ToString() + ",");
                        //sw.Write(",");
                        //sw.Write(sw.NewLine);
                    }
                    else
                    {
                        Point cR = GetCenter(presenR);
                        Point pR = GetCenter(prevR);
                        Point ver = new Point(cR.X - pR.X, cR.Y - pR.Y);

                        if (!(ver.X == 0 && ver.Y == 0))
                        {
                            DateTime ct = timeList[i];
                            sw.Write(ct.ToString() + ",");

                            sw.Write(GetInnerProductCos(new Point(1, 0), ver).ToString() + ",");
                            sw.Write(GetInnerProductCos(new Point(0, 1), ver).ToString() + ",");
                            sw.Write(sw.NewLine);
                        }
                        else
                        {
                            if ((presenR.Contains(prevR) || prevR.Contains(presenR)) && !prevR.Equals(presenR))
                            {
                                //zoom対策
                                //現在はzoomと平行移動を同時に行うことは普通しない。
                                //よってzoomが判別されれば、それはz軸方向のみの移動なので
                                //内積は必ずゼロになる（角度90度）
                                //z軸方向の＋－はどちらでもよい。
                                DateTime ct = timeList[i];
                                sw.Write(ct.ToString() + ",");

                                sw.Write("0,");
                                sw.Write("0,");
                                sw.Write(sw.NewLine);
                            }
                        }
                    }
                    prevR = presenR;
                }
                sw.Close();
            }
        }

        private static Point GetCenter(Rectangle r)
        {
            return new Point(r.X + r.Width / 2, r.Y + r.Height / 2);
        }

        private static double Norm(Point p)
        {
            return Math.Sqrt(p.X * p.X + p.Y * p.Y);
        }
        private static double GetInnerProductCos(Point p1, Point p2)
        {
            return (p1.X * p2.X + p1.Y * p2.Y) / (double)(Norm(p1) * Norm(p2));
        }
        #endregion
    }
}
